package com.gitee.l0km.xthrift.thrift.protocol;

import static com.google.common.base.Strings.isNullOrEmpty;
import static com.gitee.l0km.xthrift.thrift.protocol.TProtocolSupport.getFieldName;
import static com.gitee.l0km.xthrift.thrift.protocol.TProtocolSupport.setTypeOfField;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolException;
import org.apache.thrift.protocol.TProtocolUtil;
import org.apache.thrift.protocol.TType;

import com.facebook.swift.codec.ThriftCodec;
import com.facebook.swift.codec.ThriftProtocolType;
import com.facebook.swift.codec.internal.TProtocolReader;
import com.facebook.swift.codec.internal.UnknownEnumValueException;
import com.facebook.swift.codec.internal.builtin.ByteThriftCodec;
import com.facebook.swift.codec.internal.builtin.DoubleThriftCodec;
import com.facebook.swift.codec.internal.builtin.IntegerThriftCodec;
import com.facebook.swift.codec.internal.builtin.LongThriftCodec;
import com.facebook.swift.codec.internal.builtin.ShortThriftCodec;
import com.facebook.swift.codec.internal.builtin.StringThriftCodec;
import com.gitee.l0km.xthrift.thrift.EnumThriftCodec;
import com.gitee.l0km.xthrift.thrift.codec.ListThriftCodecXthrift;
import com.gitee.l0km.xthrift.thrift.codec.MapThriftCodecXthrift;
import com.gitee.l0km.xthrift.thrift.codec.SetThriftCodecXthrift;
import com.google.common.primitives.Booleans;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;

/**
 * 基于{@link TProtocolReader}重写{@link #skipFieldData()}方法，
 * 当为JSON Protocol({@link TJSONProtocolXthrift})时调用定制实现{@link #skip(TJSONProtocolXthrift)}
 * @author guyadong
 * @since 1.3.2
 *
 */
public class TProtocolReaderXthrift extends TProtocolReader {
	private static final StringThriftCodec STRING_CODEC = new StringThriftCodec();
	private static final DoubleThriftCodec DEOBLE_CODEC = new DoubleThriftCodec();
	private static final ByteThriftCodec BYTE_CODEC = new ByteThriftCodec();
	private static final ShortThriftCodec I16_CODEC = new ShortThriftCodec();
	private static final IntegerThriftCodec  I32_CODEC = new IntegerThriftCodec();
	private static final LongThriftCodec  I64_CODEC = new LongThriftCodec();
	private TProtocolReaderXthrift(TProtocol protocol) {
		super(protocol);
	}
	@Override
	public boolean[] readBoolArray() throws TException {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if (null != protocol) {
			protocol.readListBegin();
	        ArrayList<Boolean> buf = new ArrayList<>(16);
			while (protocol.peek() != TJSONProtocolXthrift.RBRACKET[0]) {
	        	buf.add(readBool());
	        }
			protocol.readListEnd();
	        return Booleans.toArray(buf);
		}
		return super.readBoolArray();
	}


	@Override
	public short[] readI16Array() throws TException {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if(null != protocol) {
	        protocol.readListBegin();
	        ArrayList<Short> buf = new ArrayList<>(16);
			while (protocol.peek() != TJSONProtocolXthrift.RBRACKET[0]) {
	        	buf.add(readI16());
	        }
	        protocol.readListEnd();
	        return Shorts.toArray(buf);
		}
		return super.readI16Array();
	}


	@Override
	public int[] readI32Array() throws TException {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if(null != protocol) {
	        protocol.readListBegin();
	        ArrayList<Integer> buf = new ArrayList<>(16);
			while (protocol.peek() != TJSONProtocolXthrift.RBRACKET[0]) {
	        	buf.add(readI32());
	        }
	        protocol.readListEnd();
	        return Ints.toArray(buf);
		}
		return super.readI32Array();
	}


	@Override
	public long[] readI64Array() throws TException {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if(null != protocol) {
	        protocol.readListBegin();
	        ArrayList<Long> buf = new ArrayList<>(16);
			while (protocol.peek() != TJSONProtocolXthrift.RBRACKET[0]) {
	        	buf.add(readI64());
	        }
	        protocol.readListEnd();
	        return Longs.toArray(buf);
		}
		return super.readI64Array();
	}


	@Override
	public double[] readDoubleArray() throws TException {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if(null != protocol) {
	        protocol.readListBegin();
	        ArrayList<Double> buf = new ArrayList<>(16);
			while (protocol.peek() != TJSONProtocolXthrift.RBRACKET[0]) {
	        	buf.add(readDouble());
	        }
	        protocol.readListEnd();
	        return Doubles.toArray(buf);
		}
		return super.readDoubleArray();
	}


	@Override
	public <E> Set<E> readSet(ThriftCodec<E> elementCodec) throws Exception {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if(null != protocol) {
			elementCodec = replaceIfNeed(elementCodec);
	        protocol.readSetBegin();
	        Set<E> set = new HashSet<>();
	        while (protocol.peek() != TJSONProtocolXthrift.RBRACKET[0]) {
	            try {
	                E element = elementCodec.read(protocol);
	                set.add(element);
	            } catch (UnknownEnumValueException e) {
	              // continue
	            }
	        }
	        protocol.readSetEnd();
	        return set;
		}
		return super.readSet(elementCodec);
	}


	@Override
	public <E> List<E> readList(ThriftCodec<E> elementCodec) throws Exception {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if(null != protocol) {
			elementCodec = replaceIfNeed(elementCodec);
	        protocol.readListBegin();
	        List<E> list = new ArrayList<>();
	        while (protocol.peek() != TJSONProtocolXthrift.RBRACKET[0]) {
	            try {
	                E element = elementCodec.read(protocol);
	                list.add(element);
	            } catch (UnknownEnumValueException e) {
	              // continue
	            }
	        }
	        protocol.readListEnd();
	        return list;
		}
		return super.readList(elementCodec);
	}

	@Override
	public <K, V> Map<K, V> readMap(ThriftCodec<K> keyCodec, ThriftCodec<V> valueCodec) throws Exception {
		TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if(null != protocol) {
			keyCodec = replaceIfNeed(keyCodec);
			valueCodec = replaceIfNeed(valueCodec);
	        protocol.readMapBegin();
	        Map<K, V> map = new HashMap<>();
	        while (protocol.peek() != TJSONProtocolXthrift.RBRACE[0]) {
	            try {
	                K key = keyCodec.read(protocol);
	                V value = valueCodec.read(protocol);
	                map.put(key, value);
	            } catch (UnknownEnumValueException e) {
	              // continue
	            }
	        }
	        protocol.readMapEnd();
	        return map;
		}
		return super.readMap(keyCodec, valueCodec);
	}

	@Override
	public Object readField(ThriftCodec<?> codec) throws Exception {
		if(TProtocolSupport.isJSONXthrift(getProtocol())) {
			if(!usingMatchedCodecTypeOnInvalid(codec)) {
				return null;
			}
			codec = replaceIfNeed(codec);
		}
		return super.readField(codec);
	}

	@Override
	public <T extends Enum<T>> T readEnumField(ThriftCodec<T> enumCodec) throws Exception {
		if(TProtocolSupport.isJSONXthrift(getProtocol())) {
			if(!usingMatchedCodecTypeOnInvalid(enumCodec)) {
				return null;
			}
			enumCodec = replaceIfNeed(enumCodec);
		}
		return super.readEnumField(enumCodec);
	}

	@Override
	public void skipFieldData() throws TException {
		TJSONProtocolXthrift jsonsProtocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
		if (null != jsonsProtocol) {
			skip(jsonsProtocol);
		} else {
			super.skipFieldData();
		}
	}
	/**
	 * @param protocol
	 * @param type
	 * @throws TException
	 * @since 1.5.1
	 */
	public static void skip(TProtocol protocol, byte type) throws TException {
		TJSONProtocolXthrift jsonsProtocol = TProtocolSupport.fetchJSONXthrift(protocol);
		if (null != jsonsProtocol) {
			jsonsProtocol.skip(type);
		} else {
			TProtocolUtil.skip(protocol, type);
		}
	}

	/**
	 * 如果字段为字符串直接返回
	 * 如果字段为数字转为HEX字符串
	 * @throws Exception
	 * @since 1.5.0
	 */
	public String readStringOrNumberAsHex() throws Exception {
		switch (getFieldType()) {
		case TType.STRING:
			return (String) super.readField(STRING_CODEC);
		case TType.BYTE:
			return toHex(((Byte) super.readField(BYTE_CODEC)));
		case TType.I16:
			return toHex(((Short) super.readField(I16_CODEC)));
		case TType.I32:
			return toHex(((Integer) super.readField(I32_CODEC)));
		case TType.I64:
			return toHex(((Long) super.readField(I64_CODEC)));
		case -1: {
			TJSONProtocolXthrift protocol = TProtocolSupport.fetchJSONXthrift(getProtocol());
			if (null != protocol) {
				if (protocol.matchedCurrentType(ThriftProtocolType.STRING)) {
					return (String) super.readField(STRING_CODEC);
				} else if (protocol.matchedCurrentType(ThriftProtocolType.DOUBLE)) {
					return toHex(((Double) super.readField(DEOBLE_CODEC)).longValue());
				}
			}
		}
		}
		throw new UnsupportedOperationException("INVALID field type "+ getFieldType());
	}
	/**
	 * cast integral Number  to HEX string
	 * @param input
	 * @return hex string or  null if not Number
	 */
	private static String toHex(Number input){
		if(input instanceof Integer) {
			return String.format("%08X",input.intValue());
		}else if(input instanceof Long) {
			return String.format("%016X",input.longValue());
		}else if(input instanceof Short) {
			return String.format("%04X",input.shortValue());
		}else if(input instanceof Byte) {
			return String.format("%02X",input.byteValue());
		}else if(null != input) {
			return String.format("%08X",input.intValue());
		}
		return null;
	}
	/**
	 * 当字段类型为无效值(-1)且猜测类型匹配codec类型时，将当前字段类型强制设置为codec 的类型
	 * @param codec
	 * @return 如果类型与codec 的要求的类型不匹配返回{@code false}
	 * @throws TException
	 * @see {@link TJSONProtocolXthrift#matchedCurrentType(ThriftProtocolType)}
	 */
	private boolean usingMatchedCodecTypeOnInvalid(ThriftCodec<?> codec) throws TException {
		if (getFieldType() == -1) {
			/** 如果输入中没有指定字段ID则尝试从字段名获取字段ID */
	    	String fieldName = getFieldName(this);
	    	if(isNullOrEmpty(fieldName)) {
	    		throw new TProtocolException("MISS REQUIRED field name");
	    	}
	    	TJSONProtocolXthrift protocol= TProtocolSupport.fetchJSONXthrift(getProtocol());
	    	ThriftProtocolType protocolType = codec.getType().getProtocolType();
	    	if(protocol.matchedCurrentType(protocolType)) {
	    		/** 没有解析到类型时及当前猜测类型也匹配codec类型时，强制设置为codec 的类型 */
	    		setTypeOfField(this,protocolType.getType());
	    	}else {
	    		return false;
	    	}
		}
		return true;
	}
	/**
	 * 根据需要替换编解码器<br>
	 * 如果是枚举类型编解码器则替换为自定义的{@link EnumThriftCodec}实例以支持不带类型的标准JSON格式数据协议 
	 * @param <T>
	 * @param codec
	 */
	private <T> ThriftCodec<T> replaceIfNeed(ThriftCodec<T> codec){
		ThriftCodec<T> replaced = EnumThriftCodec.usingIfEnum(codec);
		replaced = ListThriftCodecXthrift.usingIfList(replaced);
		replaced = SetThriftCodecXthrift.usingIfSet(replaced);
		replaced = MapThriftCodecXthrift.usingIfMap(replaced);
		return replaced;
	}
	private void skip(TJSONProtocolXthrift protocol) throws TException {
		protocol.skip(getFieldType());
		protocol.readFieldEnd();
		TProtocolSupport.setCurrentField(this,null);
	}
	
	public static TProtocolReader of(TProtocol protocol) {
		checkNotNull(protocol, "protocol is null");
		if(TProtocolSupport.isJSONXthrift(protocol)) {
			return new TProtocolReaderXthrift(protocol);
		}
		return new TProtocolReader(protocol);
	}
}
